﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;

namespace Camstar.Utils.csv
{
    /// <summary>
    /// Extends StreamReader to read a stream containing rows of Character Separated Values.
    /// </summary>
    public class CsvWriter : CsvBase
    {
        #region Constructors

        /// <summary>
        /// Base constructor for this instance.
        /// </summary>
        /// <param name="writer">The text writer to be written.</param>
        /// <param name="isLocal">A value indicating if the text writer was created by this instance.</param>
        private CsvWriter(
            TextWriter writer,
            bool isLocal)
        {
            isLocalWriter = isLocal;
            BaseWriter = writer;
        }

        /// <summary>
        /// Initializes a new instance the CsvReader class for the specified text writer.
        /// </summary>
        /// <param name="writer">The text writer to be written.</param>
        public CsvWriter(
            TextWriter writer)
        {
            BaseWriter = writer;
        }

        /// <summary>
        /// Initializes a new instance of the CsvWriter class for the specified
        /// stream. 
        /// </summary>
        /// <param name="stream">The stream to be written.</param>
        /// <exception cref="System.ArgumentException">The stream does not support writing.</exception>
        /// <exception cref="System.ArgumentNullException">stream is null.</exception>
        public CsvWriter(
            Stream stream)
            : this(new StreamWriter(stream), true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the CsvWriter class for the specified
        /// file name.
        /// </summary>
        /// <param name="path">The complete file path to be written.</param>
        /// <exception cref="System.ArgumentException">path is an empty string (""). -or- path contains the name of a system device (com1, com2, etc).</exception>
        /// <exception cref="System.ArgumentNullException">path is null.</exception>
        /// <exception cref="System.IO.DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception>
        /// <exception cref="System.IO.IOException">path includes an incorrect or invalid syntax for file name, directory name, or volume label syntax.</exception>
        /// <exception cref="System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
        /// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
        /// <exception cref="System.UnauthorizedAccessException">Access is denied.</exception>
        public CsvWriter(
            string path)
            : this(new StreamWriter(path), true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the CsvWriter class for the specified
        /// stream, with the specified character encoding.
        /// </summary>
        /// <param name="stream">The stream to be written.</param>
        /// <param name="encoding">The character encoding to use.</param>
        /// <exception cref="System.ArgumentException">The stream does not support writing.</exception>
        /// <exception cref="System.ArgumentNullException">stream or encoding is null.</exception>
        public CsvWriter(
            Stream stream,
            Encoding encoding)
            : this(new StreamWriter(stream, encoding), true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the CsvWriter class for the specified
        /// file name, and file appending option.
        /// </summary>
        /// <param name="path">The complete file path to be written.</param>
        /// <param name="append">Determines whether data is to be appended to the file. If the file exists and append is false, the file is overwritten. If the file exists and append is true, the data is appended to the file. Otherwise, a new file is created.</param>
        /// <exception cref="System.ArgumentException">path is an empty string (""). -or- path contains the name of a system device (com1, com2, etc).</exception>
        /// <exception cref="System.ArgumentNullException">path is null.</exception>
        /// <exception cref="System.IO.DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception>
        /// <exception cref="System.IO.IOException">path includes an incorrect or invalid syntax for file name, directory name, or volume label syntax.</exception>
        /// <exception cref="System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
        /// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
        /// <exception cref="System.UnauthorizedAccessException">Access is denied.</exception>
        public CsvWriter(
            string path,
            bool append)
            : this(new StreamWriter(path, append), true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the CsvWriter class for the specified
        /// stream, with the specified character encoding, and buffer size.
        /// </summary>
        /// <param name="stream">The stream to be written.</param>
        /// <param name="encoding">The character encoding to use.</param>
        /// <param name="bufferSize">The minimum buffer size, in number of 16-bit characters.</param>
        /// <exception cref="System.ArgumentException">The stream does not support writing.</exception>
        /// <exception cref="System.ArgumentNullException">stream or encoding is null.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">bufferSize is negative.</exception>
        public CsvWriter(
            Stream stream,
            Encoding encoding,
            int bufferSize)
            : this(new StreamWriter(stream, encoding, bufferSize), true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the CsvWriter class for the specified
        /// file name, with the specified character encoding, and file appending
        /// option.
        /// </summary>
        /// <param name="path">The complete file path to be written.</param>
        /// <param name="encoding">The character encoding to use.</param>
        /// <param name="append">Determines whether data is to be appended to the file. If the file exists and append is false, the file is overwritten. If the file exists and append is true, the data is appended to the file. Otherwise, a new file is created.</param>
        /// <exception cref="System.ArgumentException">path is an empty string (""). -or- path contains the name of a system device (com1, com2, etc).</exception>
        /// <exception cref="System.ArgumentNullException">path or encoding is null.</exception>
        /// <exception cref="System.IO.DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception>
        /// <exception cref="System.IO.IOException">path includes an incorrect or invalid syntax for file name, directory name, or volume label syntax.</exception>
        /// <exception cref="System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
        /// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
        /// <exception cref="System.UnauthorizedAccessException">Access is denied.</exception>
        public CsvWriter(
            string path,
            bool append,
            Encoding encoding)
            : this(new StreamWriter(path, append, encoding), true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the CsvWriter class for the specified
        /// file name, with the specified character encoding, file appending
        /// option, and buffer size.
        /// </summary>
        /// <param name="path">The complete file path to be written.</param>
        /// <param name="encoding">The character encoding to use.</param>
        /// <param name="append">Determines whether data is to be appended to the file. If the file exists and append is false, the file is overwritten. If the file exists and append is true, the data is appended to the file. Otherwise, a new file is created.</param>
        /// <param name="bufferSize">The minimum buffer size, in number of 16-bit characters.</param>
        /// <exception cref="System.ArgumentException">path is an empty string (""). -or- path contains the name of a system device (com1, com2, etc).</exception>
        /// <exception cref="System.ArgumentNullException">path or encoding is null.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">bufferSize is negative.</exception>
        /// <exception cref="System.IO.DirectoryNotFoundException">The specified path is invalid, such as being on an unmapped drive.</exception>
        /// <exception cref="System.IO.IOException">path includes an incorrect or invalid syntax for file name, directory name, or volume label syntax.</exception>
        /// <exception cref="System.IO.PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
        /// <exception cref="System.Security.SecurityException">The caller does not have the required permission.</exception>
        /// <exception cref="System.UnauthorizedAccessException">Access is denied.</exception>
        public CsvWriter(
            string path,
            bool append,
            Encoding encoding,
            int bufferSize)
            : this(new StreamWriter(path, append, encoding, bufferSize), true)
        {
        }

        #endregion // Constructors

        #region Private data

        /// <summary>
        /// A value indicating if the TextWriter was created by this instance.
        /// </summary>
        private readonly bool isLocalWriter;

        /// <summary>
        /// Add a CR character before each end of row character.
        /// </summary>
        private bool isAddCr;

        /// <summary>
        /// A value indicating if we are positioned at the first column of the row.
        /// </summary>
        private bool isFirstColumn;

        /// <summary>
        /// Enclose text in an Excel formula by default.
        /// </summary>
        private bool isFormula;

        /// <summary>
        /// Treat leading and trailing white space as characters that must be escaped.
        /// </summary>
        private bool isWhiteSpace;

        #endregion // Private data

        #region Properties

        /// <summary>
        /// Gets the underlying text writer for this instance.
        /// </summary>
        public TextWriter BaseWriter { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating if DataColumn extended properties Format and FormatProvider are used.
        /// </summary>
        public bool UseDataFormat { get; set; }

        #endregion // Properties

        #region Public methods

        #region Public methods (column)

        /// <summary>
        /// Write a column containing a string value.
        /// </summary>
        /// <param name="value">The string value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            string value)
        {
            Write(value, isFormula);
        }

        /// <summary>
        /// Write a column containing a string value.
        /// </summary>
        /// <param name="value">The string value to be written.</param>
        /// <param name="useFormula">A value indicating if the value should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            string value,
            bool useFormula)
        {
            // If null string, treat as empty string
            if (value == null)
                value = string.Empty;

            // If first column, advance; otherwise, write a separator
            if (isFirstColumn)
                isFirstColumn = false;
            else
                BaseWriter.Write(EndColumnChar);

            // If not quoting...
            if (!isAllQuote)
            {
                // If invalid operation, throw an exception
                if (useFormula)
                    throw new InvalidOperationException("CsvWriter unable to quote formula.");

                // Write the unqouted value and return
                writeUnquoted(value);
            } // If not quoting...
            else
                writeQuoted(value, useFormula);
        }

        /// <summary>
        /// Write a column containing a boolean value.
        /// </summary>
        /// <param name="value">The boolean value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            bool value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing an unsigned byte value.
        /// </summary>
        /// <param name="value">The unsigned byte value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            byte value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a character value.
        /// </summary>
        /// <param name="value">The character value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            char value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a single-precision floating point value.
        /// </summary>
        /// <param name="value">The single-precision floating point value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            float value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a date/time value.
        /// </summary>
        /// <param name="value">The date/time value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            DateTime value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a decimal value.
        /// </summary>
        /// <param name="value">The decimal value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            decimal value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a double-precision floating point value.
        /// </summary>
        /// <param name="value">The double-precision floating point value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            double value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a 32-bit signed integral value.
        /// </summary>
        /// <param name="value">The 32-bit signed integral value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            int value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a 64-bit signed integral value.
        /// </summary>
        /// <param name="value">The 64-bit signed integral value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            long value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a value of undetermined type.
        /// </summary>
        /// <param name="value">The value of undertermined type to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            object value)
        {
            Write(value, isFormula, null, null);
        }

        /// <summary>
        /// Write a column containing a value of undetermined type.
        /// </summary>
        /// <param name="value">The value of undertermined type to be written.</param>
        /// <param name="useFormula">A value indicating if the value should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            object value,
            bool useFormula)
        {
            Write(value, useFormula, null, null);
        }

        /// <summary>
        /// Write a column containing a value of undetermined type.
        /// </summary>
        /// <param name="value">The value of undertermined type to be written.</param>
        /// <param name="useFormula">A value indicating if the value should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            object value,
            bool useFormula,
            string format,
            IFormatProvider formatProvider)
        {
            if (value is string)
                Write((string) value, useFormula);
            else if (value is IFormattable)
                Write(((IFormattable) value).ToString(format, formatProvider), useFormula);
            else if (value == DBNull.Value)
                Write(string.Empty, useFormula);
            else
                Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a signed byte value.
        /// </summary>
        /// <param name="value">The signed byte value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            sbyte value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a 16-bit signed integral value.
        /// </summary>
        /// <param name="value">The 16-bit signed integral value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            short value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a 32-bit unsigned integral value.
        /// </summary>
        /// <param name="value">The 32-bit unsigned integral value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            uint value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a 64-bit unsigned integral value.
        /// </summary>
        /// <param name="value">The 64-bit unsigned integral value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            ulong value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write a column containing a 16-bit unsigned integral value.
        /// </summary>
        /// <param name="value">The 16-bit unsigned integral value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void Write(
            ushort value)
        {
            Write(value.ToString(), false);
        }

        /// <summary>
        /// Write the value for the data row at the specified index.
        /// </summary>
        public void Write(
            DataRow row,
            DataColumn column,
            bool useFormula)
        {
            // Get the format and format provider for the column
            IFormatProvider formatProvider = UseDataFormat
                                                 ? (IFormatProvider) column.ExtendedProperties["FormatProvider"]
                                                 : null;
            string format = UseDataFormat ? (string) column.ExtendedProperties["Format"] : null;

            // Write the column
            Write(row[column], useFormula, format, formatProvider);
        }

        #endregion // Public methods (column)

        #region Public methods (row)

        /// <summary>
        /// Write a header row for a data table.
        /// </summary>
        /// <param name="table">The table for which the header row is written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteHeaderRow(
            DataTable table)
        {
            WriteHeaderRow(table);
        }

        /// <summary>
        /// Write a header row for a data table.
        /// </summary>
        /// <param name="table">The table for which the header row is written.</param>
        /// <param name="useFormula">A value indicating if text columns should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteHeaderRow(
            DataTable table,
            bool useFormula)
        {
            WriteRowStart();
            foreach (DataColumn column in table.Columns)
                Write(column.Caption, useFormula);
            WriteRowEnd();
        }

        /// <summary>
        /// Write a row.
        /// </summary>
        /// <param name="row">The data row to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteRow(
            DataRow row)
        {
            WriteRow(row, isFormula);
        }

        /// <summary>
        /// Write a row.
        /// </summary>
        /// <param name="row">The data row to be written.</param>
        /// <param name="useFormula">A value indicating if text columns should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteRow(
            DataRow row,
            bool useFormula)
        {
            // Write the start of the row
            WriteRowStart();

            // If the row is not attached to a table...
            if (row.Table == null)
            {
                // Loop to write each value in the row...
                foreach (object value in row.ItemArray)
                    Write(value, useFormula);
            } // If the row is not attached to a table...
            else
            {
                // ELSE: If the row is not attached to a table...
                // Loop to write each value in the row...
                foreach (DataColumn column in row.Table.Columns)
                    Write(row, column, useFormula);
            } // ELSE: If the row is not attached to a table...

            // Write the end of the row
            WriteRowEnd();
        }

        /// <summary>
        /// Write a row.
        /// </summary>
        /// <param name="list">An enumerable list of values to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteRow(
            IEnumerable<string> list)
        {
            WriteRow(list, isFormula);
        }

        /// <summary>
        /// Write a row.
        /// </summary>
        /// <param name="list">An enumerable list of values to be written.</param>
        /// <param name="useFormula">A value indicating if text columns should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteRow(
            IEnumerable<string> list,
            bool useFormula)
        {
            // Write the start of the row
            WriteRowStart();

            // Loop to write each value in the list...
            foreach (string value in list)
                Write(value, useFormula);

            // Write the end of the row
            WriteRowEnd();
        }

        /// <summary>
        /// Write a row.
        /// </summary>
        /// <param name="list">An enumerable list of values to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteRow(
            IEnumerable list)
        {
            WriteRow(list, isFormula);
        }

        /// <summary>
        /// Write a row.
        /// </summary>
        /// <param name="list">An enumerable list of values to be written.</param>
        /// <param name="useFormula">A value indicating if text columns should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteRow(
            IEnumerable list,
            bool useFormula)
        {
            // Write the start of the row
            WriteRowStart();

            // Loop to write each value in the list...
            foreach (object value in list)
                Write(value, useFormula);

            // Write the end of the row
            WriteRowEnd(false);
        }

        /// <summary>
        /// Write an empty row.
        /// </summary>
        public void WriteRowEmpty()
        {
            // If already in a row, throw an exception
            if (isInRow)
                throw new InvalidOperationException("CsvWriter is in a row.");

            // If we do not allow empty lines, throw an exception
            if (!isEmptyLine)
                throw new InvalidOperationException("CsvOption.EmptyLine is not enabled.");

            // Enter and end the row
            isInRow = true;
            WriteRowEnd();
        }

        /// <summary>
        /// Write the end of the row.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">CsvWriter is already in a row.</exception>
        public void WriteRowEnd()
        {
            WriteRowEnd(false);
        }

        /// <summary>
        /// Write the end of the row.
        /// </summary>
        /// <param name="disableEvent">A value indicating if the RowEnd event is disabled.</param>
        /// <exception cref="System.InvalidOperationException">CsvWriter is already in a row.</exception>
        public void WriteRowEnd(
            bool disableEvent)
        {
            // If we are already in a row, throw an exception
            if (!isInRow)
                throw new InvalidOperationException("CsvWriter is not in a row.");

            // If preceeding end of row with CR, do so
            if (isAddCr)
                BaseWriter.Write('\r');

            // Write the end of row character
            BaseWriter.Write(EndRowChar);

            // Clear row indicator
            isInRow = false;

            // If requested, raise the RowEnd event
            if (!disableEvent)
                OnRowEnd(EventArgs.Empty);
        }

        /// <summary>
        /// Write the start of the row.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">CsvWriter is not in a row.</exception>
        public void WriteRowStart()
        {
            // If we are not in a row, throw an exception
            if (isInRow)
                throw new InvalidOperationException("CsvWriter is already in a row.");

            // Position to first column of the row
            isFirstColumn = true;
            isInRow = true;
        }

        #endregion // Public methods (row)

        #region Public methods (table)

        /// <summary>
        /// Write a data table.
        /// </summary>
        /// <param name="table">The table to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteTable(
            DataTable table)
        {
            WriteTable(table, true, isFormula);
        }

        /// <summary>
        /// Write a data table.
        /// </summary>
        /// <param name="table">The table to be written.</param>
        /// <param name="hasHeader">A value indicating if a header row should be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteTable(
            DataTable table,
            bool hasHeader)
        {
            WriteTable(table, hasHeader, isFormula);
        }

        /// <summary>
        /// Write a data table.
        /// </summary>
        /// <param name="table">The table to be written.</param>
        /// <param name="hasHeader">A value indicating if a header row should be written.</param>
        /// <param name="useFormula">A value indicating if text columns should be enclosed in Excel formulas.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        /// <exception cref="System.InvalidOperationException">CsvWriter is incorrectly positioned -OR- unable to escape a character -OR- unable to quote a formula.</exception>
        public void WriteTable(
            DataTable table,
            bool hasHeader,
            bool useFormula)
        {
            // Reset the cancellation flag
            CancellationPending = false;

            // If we have a header row, write the header row
            if (hasHeader)
                WriteHeaderRow(table, useFormula);

            // Loop to write each of the rows...
            foreach (DataRow row in table.Rows)
                if (CancellationPending)
                    break;
                else
                    WriteRow(row, useFormula);
        }

        #endregion Public methods (table)

        #region Public methods (other)

        /// <summary>
        /// Write a comment.
        /// </summary>
        /// <param name="comment">The comment to be written.</param>
        public void WriteComment(
            string comment)
        {
            // If comments are not supported, throw an exception
            if (!isComment)
                throw new InvalidOperationException("CsvOption.Comment is not enabled.");

            // If in-line comments are not supported, throw an exception
            if (isInRow && !isCommentInLine)
                throw new InvalidOperationException(
                    "CsvWriter is already in a row (and CsvOption.CommentLine is enabled).");

            // Write the comment character
            BaseWriter.Write(CommentChar);

            // Write the comment
            BaseWriter.Write(comment);

            // Enter and exit the row
            isInRow = true;
            WriteRowEnd();
        }

        #endregion // Public methods (other)

        #endregion // Public methods

        #region Private methods

        /// <summary>
        /// Get a value indicating if the specified character is white space.
        /// </summary>
        /// <param name="chr">The character to be tested.</param>
        /// <returns>A value indicating if the specified character is white space.</returns>
        private bool isWhiteSpaceChar(
            char chr)
        {
            return chr != EndColumnChar && chr != EndRowChar && (isAddCr && chr != '\r') && Char.IsWhiteSpace(chr);
        }

        /// <summary>
        /// Write a quoted value.
        /// </summary>
        /// <param name="value">The value to be written.</param>
        /// <param name="useFormula">A value indicating if the column should be enclosed in an Excel formula.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        private void writeQuoted(
            string value,
            bool useFormula)
        {
            // Determine the quoting character we will use
            char quoteChar = isQuote ? '"' : '\'';

            // Always quote formulas
            bool mustQuote = useFormula;

            // If we have a formula...
            if (useFormula)
            {
                // Start the formula
                BaseWriter.Write('=');
            } // If we have a formula...

            // If we are quoting leading / trailing white space...
            if (!mustQuote && isWhiteSpace)
            {
                // Get the length of the string
                int length = value.Length;

                // Determine if we have leading or trailing white space
                mustQuote = length > 0 && (isWhiteSpaceChar(value[0]) || isWhiteSpaceChar(value[length - 1]));
            } // If we are quoting leading / trailing white space...

            // If we still are not quoting...
            if (!mustQuote)
            {
                // Loop for each character to determine if we must escape anything...
                foreach (char nextChar in value)
                    if (nextChar == EndColumnChar || nextChar == EndRowChar || nextChar == quoteChar ||
                        (isAddCr && nextChar == '\r') || (isEscape && nextChar == EscapeChar))
                    {
                        mustQuote = true;
                        break;
                    }
            } // Loop for each character to determine if we must escape anything...

            // If we do not need to quote...
            if (!mustQuote)
            {
                // Write unaltered string and return
                BaseWriter.Write(value);
                return;
            } // If we do not need to quote...

            // Start the quoting
            BaseWriter.Write(quoteChar);

            // Loop to write the value...
            foreach (char nextChar in value)
            {
                // If we have a quote character...
                if (nextChar == quoteChar)
                {
                    // If doubling, double; otherwise, if escaping, escape; otherwise, throw an exception
                    if (isDouble)
                        BaseWriter.Write(quoteChar);
                    else if (isEscape)
                        BaseWriter.Write(EscapeChar);
                    else
                        throw new InvalidOperationException("CsvWriter unable to escape quote.");
                } // If we have a quote character...
                else
                {
                    // ELSE: If we have a quote character...
                    // If we have an escape character, escape it
                    if (isEscape && (nextChar == EscapeChar))
                        BaseWriter.Write(EscapeChar);
                } // ELSE: If we have a quote character...

                // Write the character
                BaseWriter.Write(nextChar);
            } // Loop to write the value...

            // End the quoting
            BaseWriter.Write(quoteChar);
        }

        /// <summary>
        /// Write an unquoted value.
        /// </summary>
        /// <param name="value">The value to be written.</param>
        /// <exception cref="System.ObjectDisposedException">The underlying TextWriter is closed.</exception>
        /// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
        private void writeUnquoted(
            string value)
        {
            // Set initial assumptions
            bool inLead = isWhiteSpace;
            int length = value.Length;
            int startTrail = length;

            // If we must escape trailing white space, calculate where to start
            if (isWhiteSpace && length > 0 && isWhiteSpaceChar(value[length - 1]))
                while (isWhiteSpaceChar(value[startTrail - 1]))
                    startTrail--;

            // Loop to write out the value...
            for (int index = 0; index < length; index++)
            {
                // Get the next character
                char nextChar = value[index];

                // If we may be in leading white space, check if we are
                if (inLead)
                    inLead = isWhiteSpaceChar(nextChar);

                // If we must escape this character, do so
                if (inLead || index > startTrail || (isFreeEscape && nextChar == EscapeChar) ||
                    nextChar == EndColumnChar || nextChar == EndRowChar || (isAddCr && nextChar == '\r'))
                    if (isFreeEscape)
                        BaseWriter.Write(EscapeChar);
                    else
                        throw new InvalidOperationException("CsvWriter unable to escape special character.");

                // Write the character
                BaseWriter.Write(nextChar);
            } // Loop to write out the value...
        }

        #endregion // Private methods

        #region Protected methods

        /// <summary>
        /// Update option variables based on value of options.
        /// </summary>
        protected override void UpdateOptions()
        {
            isWhiteSpace = (options & CsvOption.WhiteSpace) != 0;
            isFormula = (options & CsvOption.Formula) != 0;
            isAddCr = (options & CsvOption.AddCr) != 0;
            base.UpdateOptions();
        }

        #endregion // Protected methods

        /// <summary>
        /// Dispose of resources associated with this instance.
        /// </summary>
        public override void Dispose()
        {
            // If nothing to dispose, just return
            if (!isLocalWriter || BaseWriter == null)
                return;

            // Dispose of the text writer
            try
            {
                BaseWriter.Dispose();
            }
            finally
            {
                BaseWriter = null;
            }
        }
    }
}